package pl.edu.fuw.fft;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Random;

public class FFT {

	private static final String FILLED_SIGNAL_FILE = "FILLED_SIGNAL_FILE";
	private static final String SPECTRUM_FILE = "SPECTRUM_FILE";
	@SuppressWarnings("unused")
	private static final String DEFAULT_SPECTRUM_FILE = "data/spectrum.txt";
	private static final String DEFAULT_PREFIX = "DEFAULT_";
	private static final String OUTPUT_FILE = "OUTPUT_FILE";
	private static final String DEFAULT_INPUT_FILE = "data/signal.txt";
	private static final String INPUT_FILE = "INPUT_FILE";
	@SuppressWarnings("unused")
	private static final String DEFAULT_OUTPUT_FILE = "data/filtered_signal.txt";
	private static final String DEFAULT_NOISE_FACTOR = "0.1";
	private static final String NOISE_FACTOR = "NOISE_FACTOR";
	private static final String FILTERED_SPECTRUM_FILE = "FILTERED_SPECTRUM_FILE";
	@SuppressWarnings("unused")
	private static final String DEFAULT_FILTERED_SPECTRUM_FILE = "data/filtered_spectrum.txt";
	private static final String NOISED_SIGNAL = "NOISED_SIGNAL";
	@SuppressWarnings("unused")
	private static final String DEFAULT_NOISED_SIGNAL = "data/noised_signal.txt";
	@SuppressWarnings("unused")
	private static final String DEFAULT_FILLED_SIGNAL_FILE = "data/filled_signal.txt";

	private static Properties props;

	private static double[] timeAxis;
	private static double[] freqAxis;
	
	public static Complex[] computeFFT(Complex[] signal) {
		int N = signal.length;
		if (N == 1)
			return new Complex[] { new Complex(signal[0]) };
		if (N % 2 != 0) {
			throw new IllegalArgumentException("Signal length isn't power of 2");
		}
		Complex[] even = new Complex[N / 2];
		for (int i = 0; i < even.length; i++) {
			even[i] = signal[2 * i];
		}
		Complex[] x = computeFFT(even);
		Complex[] odd = new Complex[N / 2];
		for (int i = 0; i < even.length; i++) {
			odd[i] = signal[2 * i + 1];
		}
		Complex[] y = computeFFT(odd);
		Complex[] spectrum = new Complex[N];
		for (int i = 0; i < N/2; i++) {
			double phi = -2 * Math.PI * i;
			Complex W = new Complex(Math.cos(phi), Math.sin(phi));
			spectrum[i] = x[i].add(y[i].multiplicate(W));
			spectrum[i + N/2] = x[i].subtract(y[i].multiplicate(W));
		}

		return spectrum;
	}

	public static Complex[] computeIFFT(Complex[] spectrum) {
		int N = spectrum.length;
		Complex[] signal = new Complex[N];
		signal = Complex.conjugateAll(spectrum);
		signal = computeFFT(signal);
		signal = Complex.conjugateAll(signal);
		signal = Complex.divideAllByN(signal);
		return signal;
	}

	public FFT(){
		init();
	}
	public static void main(String[] args) {

		double frequency = 1;
		double samplingFrequency = 512;
		int samples = 1024;
		double treshold = 20;
		
		if (args.length != 1 || !args[0].equals("-d")) {
			try {
				if(args.length != 4)
					throw new NumberFormatException();
				frequency = Double.parseDouble(args[0]);
				samplingFrequency = Double.parseDouble(args[1]);
				samples = Integer.parseInt(args[2]);
				treshold = Double.parseDouble(args[3]);
			} catch (NumberFormatException e) {
				System.out.println("Incorrect values. Usage:\n"
					+ "fft [frequency] [samplingFrequency] [samples] [tresholdFreq]\n"
					+ "For default values use:\n" + "fft -d");
				System.exit(0);
			}
		}
		System.out.println("Frequency: " + frequency);
		System.out.println("Sampling Frequency: " + samplingFrequency);
		System.out.println("Samples: " + samples);
		System.out.println("Treshold Frequency: " + treshold);
		System.out.println("Computing...: ");
		FFT fft = new FFT();
//		f = 1Hz, fs = 512Hz, probek = 1024
		double[][] sinWave = Util.generateSinusoidalSignal(frequency, 
				samplingFrequency, samples);
		double[] signal = sinWave[0];
		timeAxis = sinWave[1];
		freqAxis = sinWave[2];
		FFT.saveOutputData(timeAxis, signal, INPUT_FILE);
		double[] data = fft.readInputData();
		data = fft.addNoise(data);
		FFT.saveOutputData(timeAxis, data, NOISED_SIGNAL);
		data = FFT.filter(data, (int) (treshold*samples/samplingFrequency));
		FFT.saveOutputData(timeAxis, data, OUTPUT_FILE);
		System.out.println("Done.");
	}

	public void init() {
		props = new Properties();
		try {
			props.load(new FileInputStream("conf/fft.properties"));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public double[] readInputData() {
		

		BufferedReader input = null;

		List<Double> signal = new ArrayList<Double>();
		try {
			input = new BufferedReader(new FileReader(props.getProperty(
					INPUT_FILE, DEFAULT_INPUT_FILE)));
			String line;
			while ((line = input.readLine()) != null) {
				String[] data = line.split("\t");
				signal.add(new Double(data[1]));
			}
			input.close();

		} catch (Exception e) {
			e.printStackTrace();
		}
		int diff;
		if ((diff = checkInputLength(signal.size())) != 0) {
			for (int i = 0; i < diff; i++) {
				signal.add(new Double(0));
			}
		}
		double[] array = FFT.generateArray(signal);
		FFT.saveOutputData(timeAxis, array, FILLED_SIGNAL_FILE);
		return array;
	}

	public int checkInputLength(int length){
		int nearest = 2;
		while (nearest < length) {
			nearest *= 2;
		}
		return nearest - length;
	}
	public static void saveOutputData(double[] domain, double[] signal, 
			String target) {
		BufferedWriter output = null;
		try {
			output = new BufferedWriter(new FileWriter(props.getProperty(
					target,DEFAULT_PREFIX + target)));
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			for (int i = 0; i < signal.length; i++) {
				output.append(domain[i] + "\t" + signal[i]);
				output.newLine();
			}
			output.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	public double[] addNoise(double[] signal) {
		Random rnd = new Random();
		double noiseFactor = Double.parseDouble(props.getProperty(NOISE_FACTOR, 
				DEFAULT_NOISE_FACTOR));
		for (int i = 0; i < signal.length; i++) {
			signal[i] = signal[i] + (rnd.nextDouble() - 0.5) * noiseFactor;
		}
		return signal;
	}
	
	public static double[] generateArray(List<Double> doubles){
		int n = doubles.size();
		double[] array = new double[n];
		for (int i = 0; i < n; i++) {
			array[i] = doubles.get(i).doubleValue();
		}
		return array;
	}
	
	public static double[] filter(double[] signal, int freq) {
		int n = signal.length;
		if (freq > n) 
			throw new IllegalArgumentException("Invalid threshold frequency!");
		Complex[] complexSignal= Complex.getArrayFromReals(signal);
		complexSignal = FFT.computeFFT(complexSignal);
		FFT.saveOutputData(freqAxis, Complex.getMagnitude(complexSignal), SPECTRUM_FILE);
		Arrays.fill(complexSignal, freq, n, new Complex(0,0));
		FFT.saveOutputData(freqAxis, Complex.getMagnitude(complexSignal), FILTERED_SPECTRUM_FILE);
		complexSignal = FFT.computeIFFT(complexSignal);
		return Complex.generateArrayOfReals(complexSignal);
		
	}

}
